/*
 * Created on Dec 31, 2004
 *
 * TODO To change the template for this generated file go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
package org.scohen.juploadr.app;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.logging.LogFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.scohen.juploadr.app.geo.GeoLocation;
import org.scohen.juploadr.app.tags.Tag;
import org.scohen.juploadr.event.RescaleListener;
import org.scohen.juploadr.ui.MessageUtils;
import org.scohen.juploadr.ui.ReusableUIFactory;
import org.scohen.juploadr.ui.thumbnail.Thumbnail;
import org.scohen.juploadr.ui.thumbnail.ThumbnailFactory;
import org.scohen.juploadr.ui.thumbnail.UploadThumbnail;
import org.scohen.juploadr.uploadapi.Account;
import org.scohen.juploadr.uploadapi.ImageUploadApi;
import org.scohen.juploadr.uploadapi.abilities.Abilities;

import au.com.lastweekend.jim.io.jpeg.JpegProcessingException;

import com.drew.imaging.jpeg.JpegMetadataReader;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.MetadataException;
import com.drew.metadata.exif.ExifDirectory;
import com.drew.metadata.exif.ExifReader;
import com.drew.metadata.iptc.IptcDirectory;
import com.drew.metadata.iptc.IptcReader;

/**
 * @author steve
 * 
 * TODO To change the template for this generated type comment go to Window -
 * Preferences - Java - Code Style - Code Templates
 */
public class ImageAttributes implements UploadImage {

    private JUploadr uploader;
    private int rank;
    private volatile Display parent;
    private Composite buttonList;
    private int state = STATE_NEW;
    private volatile Image thumbnail;
    private String imagePath;
    private String description;
    private String title;
    private Set<Tag> tags;
    private Thumbnail button;

    private boolean publiclyVisible;
    private boolean friendViewable;
    private boolean familyViewable;
    private boolean selected = false;
    private String key;
    private ReusableUIFactory uiFactory;
    private List<PhotoSet> photoSets;
    private String photoId;
    private int rotation;
    private Pattern extension = Pattern.compile("([^\\.]+)(\\.\\w{1,4})+$");
    private SortedMap<String, com.drew.metadata.Tag> photoMetadata;
    private ImageData imageData;
    private GeoLocation location;
    public static final int MAX_THUMBNAIL_DIMENSION = 300;

    public ImageAttributes(JUploadr uploader, Composite parent, String path) {
        this(uploader, parent, path, -1);
    }

    public ImageAttributes(JUploadr uploader, Composite parent, String path, int rotation) {
        photoSets = new ArrayList<PhotoSet>();
        uiFactory = ReusableUIFactory.getInstance();

        // set up defaults
        this.uploader = uploader;
        Account current = ImageUploadApi.getInstance().getCurrentAccount();
        if ((Account.PRIVACY_PUBLIC & current.getDefaultPrivacySetting()) != 0) {
            publiclyVisible = true;
        } else {
            publiclyVisible = false;
        }
        if ((Account.PRIVACY_FRIENDS & current.getDefaultPrivacySetting()) != 0) {
            friendViewable = true;
        }
        if ((Account.PRIVACY_FAMILY & current.getDefaultPrivacySetting()) != 0) {
            familyViewable = true;
        }
        tags = new LinkedHashSet<Tag>(current.getDefaultTags());
        getPhotoTitle(path);

        imagePath = path;
        key = imagePath;

        buttonList = parent;
        this.parent = parent.getDisplay();

        if (rotation <= 0) {
            this.rotation = calculateRotation();
        } else {
            this.rotation = rotation;
        }

        determineLocationFromMetadata();

        thumbnail = generateThumbnail(parent.getDisplay());

        button = ThumbnailFactory.newThumbnail(parent, this, SWT.PUSH | SWT.FLAT);

        button.addMouseListener(new MouseAdapter() {
            public void mouseDoubleClick(MouseEvent event) {
                ImageAttributes.this.mouseDoubleClick(event);
            }

            public void mouseDown(MouseEvent event) {
                ImageAttributes.this.mouseDown(event);
            }
        });

        // generateButtonImage(parent.getDisplay(), thumbnail.getImageData());

    }

    private int calculateRotation() {
        int rotation = 0;
        com.drew.metadata.Tag tag = getPhotoMetadata().get("Orientation");
        if (tag != null) {
            Pattern p = Pattern.compile("Rotate (\\d{2,3}) (\\w{2,3})");
            try {
                Matcher m = p.matcher(tag.getDescription());
                if (m.find()) {
                    int amount = Integer.parseInt(m.group(1));
                    String direction = m.group(2);
                    if ("CW".equalsIgnoreCase(direction)) {
                        rotation = amount;
                    } else if ("CCW".equalsIgnoreCase(direction)) {
                        rotation = 360 - amount;
                    }
                }
            } catch (MetadataException e) {
                // not much we can do here.
            }
        }
        return rotation;
    }

    private void determineLocationFromMetadata() {
        com.drew.metadata.Tag latitude = getPhotoMetadata().get("GPS Latitude");
        com.drew.metadata.Tag longitude = getPhotoMetadata().get("GPS Longitude");
        com.drew.metadata.Tag latitudeRef = getPhotoMetadata().get("GPS Latitude Ref");
        com.drew.metadata.Tag longitudeRef = getPhotoMetadata().get("GPS Longitude Ref");
        if (latitude != null && longitude != null) {
            try {
                GeoLocation loc = new GeoLocation(getTitle(), getGPSFromExif(latitude.getDescription(),
                        latitudeRef.getDescription()), getGPSFromExif(longitude.getDescription(), longitudeRef
                        .getDescription()));
                loc.setAccuracy(ImageUploadApi.getInstance().getAbilities().getGeoAbilities().getMaxAccuracy());
                setLocation(loc);
            } catch (MetadataException me) {
                LogFactory.getLog(ImageAttributes.class).fatal("Could not parse exif for " + imagePath, me);
            }
        }
    }

    private double getGPSFromExif(String dms, String direction) {
        Pattern p = Pattern.compile("(\\d{1,3})\"(\\d{1,2})'(\\d{1,2}\\.?\\d*)");
        Matcher m = p.matcher(dms);
        double val = 0;
        if (m.find()) {
            val += Double.valueOf(m.group(1)); // degrees
            val += Double.valueOf(m.group(2)) / 60d; // minutes
            val += Double.valueOf(m.group(3)) / 3600d; // seconds
        }
        if ("S".equalsIgnoreCase(direction) || "W".equalsIgnoreCase(direction)) {
            val = 0 - val;
        }
        return val;
    }

    /**
     * @param path
     * @throws JpegProcessingException
     */
    private void getPhotoTitle(String path) {
        try {
            Metadata meta = new IptcReader(new File(path)).extract();
            Directory iptc = meta.getDirectory(IptcDirectory.class);
            title = iptc.getString(IptcDirectory.TAG_HEADLINE);
            description = iptc.getString(IptcDirectory.TAG_CAPTION);
        } catch (com.drew.imaging.jpeg.JpegProcessingException e) {

        }
        if (title == null) {
            title = path.substring(path.lastIndexOf(File.separator) + 1);
            Matcher matcher = extension.matcher(title);

            if (matcher.matches()) {
                title = matcher.group(1);
            }
        }
    }

    /**
     * @param display
     * @return
     */
    private Image generateThumbnail(Display display) {
        // first check to see if the photo has a thumbnail already in it.
        com.drew.metadata.Tag thumbnailData = getPhotoMetadata().get("Thumbnail Data");
        ImageData imageData = null;
        if (thumbnailData != null) {
            try {
                ExifReader reader = new ExifReader(new File(getImagePath()));
                Metadata meta = reader.extract();
                Directory exif = meta.getDirectory(ExifDirectory.class);
                byte[] thumbnailDataArray = exif.getByteArray(ExifDirectory.TAG_THUMBNAIL_DATA);
                thumbnail = new Image(display, new ByteArrayInputStream(thumbnailDataArray));
                imageData = thumbnail.getImageData();
            } catch (com.drew.imaging.jpeg.JpegProcessingException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (MetadataException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        RescaleListener listener = new RescaleListener() {
            public void rescaleDone(final Image thumb) {
                Runnable r = new Runnable() {
                    public void run() {
                        ReusableUIFactory uiFactory = ReusableUIFactory.getInstance();
                        if (ImageAttributes.this.thumbnail != null && thumbnail != uiFactory.getUnknownImage()) {
                            ImageAttributes.this.thumbnail.dispose();
                        }
                        ImageAttributes.this.thumbnail = thumb;
                        ImageAttributes.this.getButton().thumbnailChanged();
                    }
                };
                parent.syncExec(r);
            }

            public void rescaleFailed(final String message) {
                Runnable r = new Runnable() {
                    public void run() {
                        MessageUtils.showErrorMessage(message);
                        removeFromWindow();
                    }
                };
                parent.syncExec(r);
            }
        };

        RescaleThread.addImage(getImagePath(), listener);

        if (imageData == null) {
            return uiFactory.getUnknownImage();
        } else {
            return thumbnail;
        }

    }

    private void mouseDoubleClick(MouseEvent arg0) {
        openEditUI();

    }

    public void keyPressed(KeyEvent event) {
        if (event.character == SWT.DEL || event.keyCode == SWT.BS) {
            Iterator iter = uploader.getImageAttributes().values().iterator();
            while (iter.hasNext()) {
                ImageAttributes atts = (ImageAttributes) iter.next();
                if (atts.isSelected()) {
                    iter.remove();
                    atts.removeFromWindow();
                }
            }
        }

    }

    private void mouseDown(MouseEvent event) {
        if (event.button == 1 && event.stateMask != SWT.CTRL) {
            toggleSelected();
            button.redraw();
        }
    }

    /**
     * 
     */
    public void openEditUI() {
        ImageAttributesUI attrUI = new ImageAttributesUI(this);
        attrUI.show(parent);

        if (!attrUI.isCancelled()) {
            state = STATE_DATA_ENTERED;
        }
    }

    /**
     * @return Returns the button.
     */
    public Thumbnail getButton() {
        return button;
    }

    /**
     * @param button
     *            The button to set.
     */
    public void setButton(UploadThumbnail button) {
        this.button = button;
    }

    /**
     * @return Returns the description.
     */
    public String getDescription() {
        return description;
    }

    public boolean hasDescription() {
        return description != null && description.trim().length() > 0;
    }

    /**
     * @param description
     *            The description to set.
     */
    public void setDescription(String description) {
        this.description = description;
    }

    /**
     * @return Returns the tags.
     */
    public Set<Tag> getTags() {
        return tags;
    }

    public boolean hasTags() {
        return tags.size() > 0;
    }

    /**
     * @param tags
     *            The tags to set.
     */
    public void setTags(Set<Tag> tags) {
        this.tags = tags;
    }

    /**
     * @return Returns the title.
     */
    public String getTitle() {
        return title;
    }

    public boolean hasTitle() {
        return title != null && title.length() > 0 && !(imagePath.endsWith(title));
    }

    /**
     * @param title
     *            The title to set.
     */
    public void setTitle(String title) {
        this.title = title;
    }

    /**
     * @return Returns the imagePath.
     */
    public String getImagePath() {
        return imagePath;
    }

    public String getKey() {
        return key;
    }

    public boolean isRescaled() {
        return false;
    }

    /**
     * @param imagePath
     *            The imagePath to set.
     */
    public void setImagePath(String imagePath) {
        this.imagePath = imagePath;
    }

    /**
     * @return Returns the state.
     */
    public int getState() {
        return state;
    }

    /**
     * @param state
     *            The state to set.
     */
    public void setState(int state) {
        if (state == STATE_UPLOADED) {
            rank -= 100;
        }
        this.state = state;
    }

    /**
     * @return Returns the familyViewable.
     */
    public boolean isFamilyViewable() {
        return familyViewable;
    }

    /**
     * @param familyViewable
     *            The familyViewable to set.
     */
    public void setFamilyViewable(boolean familyViewable) {
        this.familyViewable = familyViewable;
    }

    /**
     * @return Returns the friendViewable.
     */
    public boolean isFriendViewable() {
        return friendViewable;
    }

    public void setSelected(boolean selected) {
        this.selected = selected;
    }

    public void toggleSelected() {
        selected = !selected;
    }

    public boolean isSelected() {
        return selected;
    }

    /**
     * @param friendViewable
     *            The friendViewable to set.
     */
    public void setFriendViewable(boolean friendViewable) {
        this.friendViewable = friendViewable;
    }

    /**
     * @return Returns the publiclyVisible.
     */
    public boolean isPubliclyVisible() {
        return publiclyVisible;
    }

    /**
     * @param publiclyVisible
     *            The publiclyVisible to set.
     */
    public void setPubliclyVisible(boolean publiclyVisible) {
        this.publiclyVisible = publiclyVisible;
    }

    /**
     * @return Returns the rank.
     */
    public int getRank() {
        return rank;
    }

    /**
     * @param rank
     *            The rank to set.
     */
    public void setRank(int rank) {
        this.rank = rank;
    }

    public void dispose() {
    }

    public void removeFromWindow() {
        state = STATE_REMOVED;
        List photoSets = getPhotoSets();
        Iterator iter = photoSets.iterator();
        while (iter.hasNext()) {
            PhotoSet set = (PhotoSet) iter.next();
            iter.remove();
            set.removePhoto(this);
        }

        if (thumbnail != uiFactory.getUnknownImage()) {
            thumbnail.dispose();
        }
        button.dispose();
        // re-sort the buttons in the UI
        buttonList.layout();
        uploader.refresh();
    }

    /**
     * @return Returns the thumbnail.
     */
    public Image getThumbnail() {
        return thumbnail;
    }

    /**
     * @param thumbnail
     *            The thumbnail to set.
     */
    public void setThumbnail(Image thumbnail) {
        this.thumbnail = thumbnail;
    }

    public void addPhotoSet(PhotoSet set) {
        if (!photoSets.contains(set)) {
            photoSets.add(set);
        }
    }

    public void removePhotoSet(PhotoSet set) {
        if (photoSets.contains(set)) {
            photoSets.remove(set);
        }
    }

    public List<? extends PhotoSet> getPhotoSets() {
        return photoSets;
    }

    public void setPhotoId(String id) {
        photoId = id;
    }

    public String getPhotoId() {
        return photoId;
    }

    /**
     * @return Returns the rotation.
     */
    public int getRotation() {
        return rotation;
    }

    /**
     * @param rotation
     *            The rotation to set.
     */
    public void setRotation(int rotation) {
        this.rotation = rotation;
    }

    public SortedMap<String, com.drew.metadata.Tag> getPhotoMetadata() {
        if (photoMetadata == null) {
            photoMetadata = new TreeMap<String, com.drew.metadata.Tag>();
            try {

                File photo = new File(getImagePath());
                Metadata metadata = JpegMetadataReader.readMetadata(photo);
                for (Iterator iter = metadata.getDirectoryIterator(); iter.hasNext();) {
                    Directory d = (Directory) iter.next();
                    for (Iterator tags = d.getTagIterator(); tags.hasNext();) {
                        com.drew.metadata.Tag tag = (com.drew.metadata.Tag) tags.next();
                        if (tag.getTagName().toLowerCase().indexOf("unknown") < 0) {
                            photoMetadata.put(tag.getTagName(), tag);
                        }
                    }
                }
            } catch (com.drew.imaging.jpeg.JpegProcessingException e) {
                // no metadata?
            }
        }
        return photoMetadata;
    }

    public ImageData getImageData() {
        if (imageData == null) {
            imageData = new ImageData(getImagePath());
        }
        return imageData;
    }

    public GeoLocation getLocation() {
        return location;
    }

    public void setLocation(GeoLocation location) {
        this.location = location;
    }

    public ImageAttributes getNext() {
        List<ImageAttributes> sorted = new ArrayList<ImageAttributes>(uploader.getImageAttributes().values());
        int idx = sorted.indexOf(this);
        if (idx < sorted.size() - 1) {
            return sorted.get(idx + 1);
        }

        return this;
    }

    public ImageAttributes getPrevious() {
        List<ImageAttributes> sorted = new ArrayList<ImageAttributes>(uploader.getImageAttributes().values());
        int idx = sorted.indexOf(this);
        if (idx > 0) {
            return sorted.get(idx - 1);
        }
        return this;
    }

    public boolean hasNext() {
        return !getNext().equals(this);
    }

    public boolean hasPrevious() {
        return !getPrevious().equals(this);
    }
}